#include "song.h"
#include <iostream>
#include <fstream>
#include "tinyxml.h"
#include "track.h"
#include "instrument.h"
#include "pattern.h"
#include "songitem.h"
#include "event.h"
using namespace std;
using namespace boost;

Song::Song(const string &name)
: m_name(name), m_bpm(120), m_rowsPerBeat(4), m_patternLength(32) {
     m_tracks.resize(0);
     // create instruments here until gui is working
     for (int i=0; i<4; i++) {
	  addInstrument(new Instrument(i, 0, ""));
     }
     createTracks(8);
}

Song::~Song() {
     clear();
}

void Song::clear() {
     while (m_tracks.size()) {
	  if (m_tracks[m_tracks.size()-1])
	       delete m_tracks[m_tracks.size()-1];
	  m_tracks.pop_back();
     }
     while (m_instruments.size()) {
	  if (m_instruments[m_instruments.size()-1])
	       delete m_instruments[m_instruments.size()-1];
	  m_instruments.pop_back();
     }
}

const string &Song::name() {
     return m_name;
}

void Song::setName(const string &name) {
     m_name = name;
}

void Song::addTrack(Track *pTrack) {
     m_tracks.push_back(pTrack);
}

void Song::removeTrack(Track *pTrack) {
     for (vector<Track *>::iterator iter=m_tracks.begin(); 
	  iter!=m_tracks.end(); iter++) {
	  if (*iter == pTrack)
	       m_tracks.erase(iter);
     }
}

void Song::removeTrackAt(int i) {
     if ((size_t)i<m_tracks.size())
	  m_tracks.erase(m_tracks.begin()+i);
}

Track *Song::trackAt(int i) {
     if ((size_t)i<m_tracks.size())
	  return m_tracks.at(i);
     return 0;
}

int Song::trackCount() {
     return (int)m_tracks.size();
}


void Song::addInstrument(Instrument *pInstrument) {
     m_instruments.push_back(pInstrument);
}

void Song::removeInstrument(Instrument *pInstrument) {
     for (vector<Instrument *>::iterator iter=m_instruments.begin(); 
	  iter!=m_instruments.end(); iter++) {
	  if (*iter == pInstrument)
	       m_instruments.erase(iter);
     }
}

void Song::removeInstrumentAt(int i) {
     if ((size_t)i<m_instruments.size())
	  m_instruments.erase(m_instruments.begin()+i);
}

Instrument *Song::instrumentAt(int i) {
     if ((size_t)i<m_instruments.size())
	  return m_instruments.at(i);
     return 0;
}

int Song::instrumentCount() {
     return (int)m_instruments.size();
}

int Song::bpm() {
     return m_bpm;
}

void Song::setBpm(int bpm) {
     if (bpm < Song::BpmMin)
	  bpm = Song::BpmMin;
     if (bpm > Song::BpmMax)
	  bpm = Song::BpmMax;
     m_bpm = bpm;
}

int Song::rowsPerBeat() {
     return m_rowsPerBeat;
}

void Song::setRowsPerBeat(int rowsPerBeat) {
     m_rowsPerBeat = rowsPerBeat;
}

int Song::patternLength()
{
     return m_patternLength;
}

void Song::setPatternLength(int length)
{
     m_patternLength = length;
}

void Song::createTracks(int num) {
     Track *pTrack = NULL;
     Pattern *pPat = NULL;
     for (int i=trackCount(); i<num; i++) {
	  pTrack = new Track;
	  pPat = new Pattern("", 0, m_patternLength);
	  pTrack->addPattern(pPat);
	  pTrack->addSongItem(new SongItem(pPat, 0));
	  addTrack(pTrack);
     }
}

int Song::length() {
     int len = 0;
     for (unsigned int i=0; i<m_tracks.size(); i++) {
	  if (m_tracks[i]->songItemCount() > len)
	       len = m_tracks[i]->songItemCount();
     }
     return len;
}

void Song::setLength(int len) {
     for (unsigned int i=0; i<m_tracks.size(); i++)
	  m_tracks[i]->setSongItemCount(len);
}

int Song::patternCount() {
     int count = 0;
     for (unsigned int i=0; i<m_tracks.size(); i++) {
	  if (m_tracks[i]->patternCount() > count)
	       count = m_tracks[i]->patternCount();
     }
     return count;
}

void Song::setPatternCount(int count) {
     for (unsigned int i=0; i<m_tracks.size(); i++)
	  m_tracks[i]->setPatternCount(count);
}

const std::string &Song::fileName() {
     return m_fileName;
}

void Song::setFileName(const std::string &fileName) {
     cerr << "setting song filename to " << fileName << endl;
     m_fileName = fileName;
}

void Song::save() {
     if (m_fileName.empty()) {
	  cerr << "song: filename not set, ignoring save" << endl;
	  return;
     }
     cerr << "writing song to " << m_fileName << endl;
     ofstream fs;
     fs.open(m_fileName.c_str());
     if (!fs.is_open()) {
	  cerr << "song: failed to open file " << m_fileName.c_str() << " for saving" << endl;
	  return;
     }
     string name;
     int count=0;
     Pattern *pPat=NULL;
     Event *pEvent=NULL;
     SongItem *pItem=NULL;
     int patnum=0;
     fs << "<?xml version=\"1.0\" ?>" << endl;
     fs << "<song name=\"" << m_name << "\" tempo=\"" << m_bpm \
	<< "\" rowsperbeat=\"" << m_rowsPerBeat \
	<< "\" patternlength=\"" << m_patternLength << "\">" << endl;
     for (int i=0; i<instrumentCount(); i++) {
	  fs << "     <instrument name=\"" << m_instruments[i]->name() \
	     << "\" channel=\"" << m_instruments[i]->channel() \
	     << "\" port=\"" << m_instruments[i]->portNum() << "\" />" << endl;
     }
     for (int i=0; i<trackCount(); i++) {
	  fs << "     <track name=\"" << m_tracks[i]->name() << "\">" << endl;
	  for (int j=0; j<m_tracks[i]->patternCount(); j++) {
	       pPat = m_tracks[i]->patternAt(j);
	       name = pPat->name();
	       fs << "          <pattern name=\"" << name << "\">" << endl;
	       count = pPat->length();
	       for (int k=0; k<count; k++) {
		    pEvent = pPat->eventAt(k);
		    fs << "               <event>" << pEvent->toString() << "</event>" << endl;
	       }
	       fs << "          </pattern>" << endl;
	  }
	  for (int j=0; j<m_tracks[i]->songItemCount(); j++) {
	       pItem = m_tracks[i]->songItemAt(j);
	       if (pItem->pattern()) {
		    patnum = pItem->pattern()->number();
		    fs << "          <songitem pattern=\"" << patnum << "\" />" << endl;
	       } else {
		    fs << "          <songitem />" << endl;
	       }
	  }
	  fs << "     </track>" << endl;
     }
     fs << "</song>" << endl;
     fs.close();
}

void Song::load() {
     if (m_fileName.empty()) {
	  cerr << "song: filename not set, ignoring load request" << endl;
	  return;
     }
     TiXmlDocument doc(m_fileName);
     if (!doc.LoadFile()) {
	  cerr << "song: failed to load file" << endl;
	  return;
     }

     // blank out song
     clear();

     TiXmlHandle hDoc(&doc);
     TiXmlElement *pSong=NULL, *pElement=NULL;
     TiXmlNode *pNode=NULL;
     int chan=0, port=0;
     string name;
     // get song
     pSong = hDoc.FirstChild("song").ToElement();
     if (!pSong) {
	  cerr << "error: file is not a 7bw song file, ignoring load request" << endl;
	  return;
     }
     name = pSong->Attribute("name");
     setName(name);
     pSong->QueryIntAttribute("tempo", &m_bpm);
     pSong->QueryIntAttribute("rowsperbeat", &m_rowsPerBeat);
     pSong->QueryIntAttribute("patternlength", &m_patternLength);
     // get instruments
     for (pNode=pSong->FirstChild("instrument"); pNode; 
	  pNode=pSong->IterateChildren("instrument",pNode)) {

	  pElement = pNode->ToElement();
	  if (!pElement)
	       continue;
	  name = pElement->Attribute("name");
	  pElement->QueryIntAttribute("channel", &chan);
	  pElement->QueryIntAttribute("port", &port);
	  addInstrument(new Instrument(chan, port, name));
	  chan=0;
	  port=0;
     }
     // get tracks
     Track *pTrack=NULL;
     TiXmlNode *pPatNode=NULL, *pEventNode=NULL;
     TiXmlElement *pPatElem=NULL, *pEventElem=NULL;
     int patNum=0, itemNum=0;
     Pattern *pPat=NULL;
     string eventStr;
     int eventNum=0;
     for (pNode=pSong->FirstChild("track"); pNode; 
	  pNode=pSong->IterateChildren("track",pNode)) {

	  pElement = pNode->ToElement();
	  if (!pElement)
	       continue;
	  name = pElement->Attribute("name");
	  pTrack = new Track();
	  pTrack->setName(name);
	  // load patterns
	  for (pPatNode=pElement->FirstChild("pattern"); pPatNode; 
	       pPatNode=pElement->IterateChildren("pattern",pPatNode)) {

	       pPatElem = pPatNode->ToElement();
	       if (!pPatElem)
		    continue;
	       name = pPatElem->Attribute("name");
	       pPat = new Pattern(name, patNum, m_patternLength);
	       // load events
	       for (pEventNode=pPatElem->FirstChild("event"); pEventNode; 
		    pEventNode=pPatElem->IterateChildren("event",pEventNode)) {

		    pEventElem = pEventNode->ToElement();
		    if (!pEventElem)
			 continue;
		    eventStr = pEventNode->FirstChild()->ToText()->Value();
		    pPat->eventAt(eventNum)->fromString(eventStr);
		    eventNum++;
	       }
	       // add pattern to track
	       pTrack->addPattern(pPat);
	       patNum++;
	       eventNum=0;
	  }
	  // load song items
	  for (pPatNode=pElement->FirstChild("songitem"); pPatNode;
	       pPatNode=pElement->IterateChildren("songitem",pPatNode)) {

	       patNum = -1;
	       pPatElem = pPatNode->ToElement();
	       if (!pPatElem)
		    continue;
	       pPatElem->QueryIntAttribute("pattern", &patNum);
	       pPat = NULL;
	       if (patNum != -1)
		    pPat = pTrack->patternAt(patNum);
	       pTrack->addSongItem(new SongItem(pPat, itemNum));
	       itemNum++;
	  }
	  // add track to song
	  addTrack(pTrack);
	  patNum = 0;
	  itemNum = 0;
     }
     m_onLoad();
}

signals2::connection Song::onLoad(const OnLoad::slot_type &callback) {
     return m_onLoad.connect(callback);
}
